﻿using ezCloud.Api.Models;
using ezCloud.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ezCloud.Api.Common
{
   
        public interface IRange<T>
        {
            T Start { get; }
            T End { get; }
            //bool Includes(T value);
            //bool Includes(IRange<T> range);
        }

        public class Formula
        {
            public string Name { get; set; }
            public DateRange Range { get; set; }
            public int Priority { get; set; }
            public string FormulaUnit { get; set; }
            public decimal ValueBefore { get; set; }
            public decimal ValueAfter { get; set; }
            public decimal FormulaValue { get; set; }
            public int FormulaPeriod { get; set; }
            public IDictionary<int, decimal> FormulaEarlyCheckIn { get; set; }
            public IDictionary<int, decimal> FormulaLateCheckOut { get; set; }
            public List<DateTime> NotAvailableDays { get; set; }
            public Formula()
            {

            }
            public Formula(string name, DateRange range, int priority, string formulaUnit, decimal valueBefore, decimal formulaValue, int formulaPeriod)
            {
                Range = range;
                Name = name;
                Priority = priority;
                FormulaUnit = formulaUnit;
                FormulaValue = formulaValue;
                FormulaPeriod = formulaPeriod;
                ValueBefore = valueBefore;
                ValueAfter = Excute(valueBefore, formulaValue, formulaPeriod);
            }
            public decimal Excute(decimal valueBefore, decimal formulaValue, int formulaPeriod)
            {
                return valueBefore + formulaValue * formulaPeriod;
            }
        }

        public class HourlyFormula
        {
            public List<Formula> hourlyFormulaList { get; set; }
            public Formula finalFormula { get; set; }
        }

        public class ExtraTraveller
        {
            public int Extra { get; set; }
            public decimal DefaultPrice { get; set; }
            public decimal ExtraPrice { get; set; }
        }

        public class DateRange : IRange<DateTime>
        {
            ezCloudDataContext context = new ezCloudDataContext();
            public int month, week, day, hour, minute;
            public DateRange rangeHour;
            public decimal totalPrice = 0.0M;
            public PlanPreview model;
            //public List<Formula> formulaList;
            public List<Formula> formulaList = new List<Formula>();
            public HourlyFormula hourlyFormula;
            public string StartString, EndString;
            public List<String> exceptionList = new List<string>();
            //public Dictionary<int, decimal> extraAdults;
            public ExtraTraveller extraAdults;
            public ExtraTraveller extraChildren;

            public DateRange()
            {

            }
            public DateRange(DateTime start, DateTime end)
            {
                StartString = start.ToString("MM/dd/yyyy HH:mm");
                EndString = end.ToString("MM/dd/yyyy HH:mm");
                Start = start; //System.DateTime.ParseExact(StartString, "MM/dd/yyyy HH:mm", System.Globalization.CultureInfo.InvariantCulture);
                End = end;//System.DateTime.ParseExact(EndString, "MM/dd/yyyy HH:mm", System.Globalization.CultureInfo.InvariantCulture);
            }
            public DateTime Start { get; set; }
            public DateTime End { get; set; }

            public bool Includes(DateTime value)
            {
                return (Start <= value) && (value <= End);
            }

            public bool Includes(IRange<DateTime> range)
            {
                return (Start <= range.Start) && (range.End <= End);
            }

            public bool Includes(TimeSpan timeSpan)
            {
                return (Start.TimeOfDay < timeSpan) && (timeSpan < End.TimeOfDay);
            }

            public IEnumerable<DateTime> EachDay(IRange<DateTime> range)
            {
                for (var day = range.Start; day.Date <= range.End.Date; day = day.AddDays(1))
                    yield return day;
            }

            public void CalculateTimeDuration()
            {
                var oneHour = 60;
                var oneDay = 24 * 60;
                var oneWeek = 7 * 24 * 60;
                var oneMonth = 30 * 24 * 60;
                
                var currentClientUser = Common.UserUtitily.getUserProfile();
                var currentHotel = context.Hotels.Where(ht => ht.HotelId == currentClientUser.HotelId).FirstOrDefault();
                this.minute = (int)(this.End - this.Start).TotalMinutes;
                if (this.model.planDetailViewModel.UseMonthlyPrice.isUsed == true)
                {
                    this.month = this.minute / oneMonth;
                    this.minute = this.minute - this.month * oneMonth;
                }
                if (this.model.planDetailViewModel.UseWeeklyPrice.isUsed == true)
                {
                    this.week = this.minute / oneWeek;
                    this.minute = this.minute - this.week * oneWeek;
                }
                if (this.model.planDetailViewModel.UseFullDayPrice.isUsed == true || this.model.planDetailViewModel.UseDayNightPrice.isUsed == true)
                {
                    this.day = this.minute / oneDay;
                    this.minute = this.minute - this.day * oneDay;
                }

                this.hour = this.minute / oneHour;
                this.minute = this.minute - this.hour * oneHour;
                if (this.minute > currentHotel.MinutesToRoundUp)
                {
                    this.hour = this.hour + 1;
                }
                /* if (range.hour < 1)
                 {
                     range.hour = 1;
                 }*/

                this.minute = 0;
                //return range;

            }

            public List<Int32> CalculateAvailableDaysOfWeek()
            {


                List<Int32> availableDaysOfWeeks = new List<Int32>();

                Dictionary<int, bool> appliedDays = new Dictionary<int, bool>();
                appliedDays.Add(0, model.planDetailViewModel.PlanSchedule.ApplyOnSunday);
                appliedDays.Add(1, model.planDetailViewModel.PlanSchedule.ApplyOnMonday);
                appliedDays.Add(2, model.planDetailViewModel.PlanSchedule.ApplyOnTuesday);
                appliedDays.Add(3, model.planDetailViewModel.PlanSchedule.ApplyOnWednesday);
                appliedDays.Add(4, model.planDetailViewModel.PlanSchedule.ApplyOnThursday);
                appliedDays.Add(5, model.planDetailViewModel.PlanSchedule.ApplyOnFriday);
                appliedDays.Add(6, model.planDetailViewModel.PlanSchedule.ApplyOnSaturday);

                foreach (KeyValuePair<int, bool> appliedDay in appliedDays)
                {
                    if (appliedDay.Value == true)
                    {
                        availableDaysOfWeeks.Add(appliedDay.Key);
                    }
                }

                return availableDaysOfWeeks;


            }

            public void CalculateListFormula()
            {

                var oneDay = 24;
                var oneWeek = 7 * 24;
                var oneMonth = 30 * 24;
                
                decimal valueBefore = 0;
                if (this.month > 0)
                {
                    DateRange rangeMonth = new DateRange(this.Start, this.Start.AddHours(oneMonth * this.month));
                    Formula monthlyFormula = new Formula("MONTHLY_PRICE", rangeMonth, 1, "MONTH(S)", valueBefore, model.planDetailViewModel.UseMonthlyPrice.price ?? 0.0M, this.month);
                    valueBefore = monthlyFormula.ValueAfter;
                    this.Start = this.Start.AddHours(oneMonth * this.month);
                    this.formulaList.Add(monthlyFormula);
                }
                if (this.week > 0)
                {
                    DateRange rangeWeek = new DateRange(this.Start, this.Start.AddHours(oneWeek * this.week));
                    Formula weeklyFormula = new Formula("WEEKLY_PRICE", rangeWeek, 2, "WEEK(S)", valueBefore, model.planDetailViewModel.UseWeeklyPrice.price ?? 0.0M, this.week);
                    valueBefore = weeklyFormula.ValueAfter;
                    this.Start = this.Start.AddHours(oneWeek * this.week);
                    this.formulaList.Add(weeklyFormula);
                }
                if (this.day > 0)
                {
                    int dayTemp = this.day;
                    List<DateTime> notAvailableDays = new List<DateTime>();
                    DateRange rangeDay = new DateRange(this.Start, this.Start.AddHours(oneDay * this.day));
                    foreach (DateTime day in EachDay(rangeDay))
                    {
                        if (!CalculateAvailableDaysOfWeek().Contains((int)day.DayOfWeek))
                        {
                            dayTemp = dayTemp - 1;
                            notAvailableDays.Add(day.Date);
                        }
                    }
                    if (dayTemp > 0)
                    {
                        Formula fullDayFormula = new Formula("FULL_DAY_PRICE", rangeDay, 3, "DAY(S)", valueBefore, model.planDetailViewModel.UseFullDayPrice.price ?? 0.0M, this.day);
                        fullDayFormula.NotAvailableDays = notAvailableDays;
                        fullDayFormula.ValueAfter = valueBefore + dayTemp * model.planDetailViewModel.UseFullDayPrice.price ?? 0.0M;
                        valueBefore = fullDayFormula.ValueAfter;
                        this.Start = this.Start.AddHours(oneDay * this.day);
                        this.formulaList.Add(fullDayFormula);
                    }
                }
                if (this.formulaList.Count > 0)
                {
                    this.totalPrice = this.formulaList[this.formulaList.Count - 1].ValueAfter;
                }
                this.rangeHour = new DateRange(this.Start, this.End);
                //return formulaList;
            }

            public void CalculateHourlyFormula()
            {
                if (hour <= 0)
                {
                    return;
                }
                else
                {
                    
                    var currentClientUser = Common.UserUtitily.getUserProfile();
                    var currentHotel = context.Hotels.Where(ht => ht.HotelId == currentClientUser.HotelId).FirstOrDefault();

                    if ((this.rangeHour.Start.Date == this.rangeHour.End.Date && !CalculateAvailableDaysOfWeek().Contains((int)this.rangeHour.Start.DayOfWeek)) || (this.rangeHour.Start.Date != this.rangeHour.End.Date && CalculateAvailableDaysOfWeek().Contains((int)this.rangeHour.Start.DayOfWeek) != CalculateAvailableDaysOfWeek().Contains((int)this.rangeHour.End.DayOfWeek)))
                    {
                        exceptionList.Add("WARNING!_TIME_DURATION_HAS_NOT_BEEN_SETTLED_IN_PLAN_SCHEDULE");
                        return;
                    }

                    List<Formula> formulaList = new List<Formula>();
                    Formula hourRemainByHourlyPriceFormula = new Formula();
                    hourRemainByHourlyPriceFormula.ValueAfter = Decimal.MaxValue;
                    Formula hourRemainByFullDayPriceFormula = new Formula("FULL_DAY_PRICE", rangeHour, 4, "HOUR(S)", totalPrice, model.planDetailViewModel.UseFullDayPrice.price ?? 0.0M, hour);
                    hourRemainByFullDayPriceFormula.ValueAfter = Decimal.MaxValue;
                    Formula hourRemainByDayNightPriceFormula = new Formula("DAY_NIGHT_PRICE", rangeHour, 4, "HOUR(S)", totalPrice, model.planDetailViewModel.UseDayNightPrice.price ?? 0.0M, hour);
                    hourRemainByDayNightPriceFormula.ValueAfter = Decimal.MaxValue;
                    if (model.planDetailViewModel.UseHourlyPrice.isUsed == true)
                    {
                        var valueAfterTemp = totalPrice;
                        var hourlyPriceAdjustment = model.planDetailViewModel.UseHourlyPrice.adjustment.Find(adj => adj.NumberOfHour == hour);
                        if (hourlyPriceAdjustment != null)
                        {
                            hourRemainByHourlyPriceFormula = new Formula("HOURLY_PRICE", rangeHour, 4, "HOUR(S)", totalPrice, hourlyPriceAdjustment.Price, hour);
                            hourRemainByHourlyPriceFormula.ValueAfter = totalPrice + hourlyPriceAdjustment.Price;
                            formulaList.Add(hourRemainByHourlyPriceFormula);
                        }

                    }
                    if (model.planDetailViewModel.UseFullDayPrice.isUsed == true)
                    {
                        var valueAfterTemp = this.totalPrice;

                        if (model.planDetailViewModel.UseEarlyCheckinFullDay.isUsed == true)
                        {
                            var currentHotelFullDayCheckinTime = currentHotel.FullDayCheckoutTime;
                            var numberOfHourFullDayEarlyCheckin = currentHotelFullDayCheckinTime.Hours - rangeHour.Start.Hour;
                            var fullDayEarlyCheckin = model.planDetailViewModel.UseEarlyCheckinFullDay.adjustment.Find(adj => adj.NumberOfHour == numberOfHourFullDayEarlyCheckin);
                            if (fullDayEarlyCheckin != null)
                            {
                                valueAfterTemp = valueAfterTemp + fullDayEarlyCheckin.Price;

                                hourRemainByFullDayPriceFormula.FormulaEarlyCheckIn = new Dictionary<int, decimal>();
                                hourRemainByFullDayPriceFormula.FormulaEarlyCheckIn.Add(numberOfHourFullDayEarlyCheckin, fullDayEarlyCheckin.Price);
                            }
                        }
                        var currentHotelFullDayCheckOutTime = currentHotel.FullDayCheckoutTime;
                        var numberOfHourFullDayAdjustment = rangeHour.End.Hour - currentHotelFullDayCheckOutTime.Hours;
                        var fullDayAdjustment = model.planDetailViewModel.UseFullDayPrice.adjustment.Find(adj => adj.NumberOfHour == numberOfHourFullDayAdjustment);
                        if (fullDayAdjustment != null)
                        {
                            valueAfterTemp = valueAfterTemp + fullDayAdjustment.Price;
                            hourRemainByFullDayPriceFormula.FormulaLateCheckOut = new Dictionary<int, decimal>();
                            hourRemainByFullDayPriceFormula.FormulaLateCheckOut.Add(numberOfHourFullDayAdjustment, fullDayAdjustment.Price);
                        }
                        valueAfterTemp = valueAfterTemp + model.planDetailViewModel.UseFullDayPrice.price ?? 0.0M;
                        hourRemainByFullDayPriceFormula.ValueAfter = valueAfterTemp;
                        formulaList.Add(hourRemainByFullDayPriceFormula);
                    }



                    if (model.planDetailViewModel.UseDayNightPrice.isUsed == true)
                    {
                        TimeSpan startTime = currentHotel.DayNightStartTime ?? default(TimeSpan);
                        TimeSpan endTime = currentHotel.DayNightEndTime ?? default(TimeSpan);
                        DateTime start = rangeHour.Start.Date + startTime;
                        DateTime end = rangeHour.End.Date.AddDays(1) + endTime;
                        if ((rangeHour.Start <= start && start <= rangeHour.End) || (start <= rangeHour.Start && rangeHour.End <= end) || (rangeHour.Start <= end && end <= rangeHour.End) || (rangeHour.Start <= start && end <= rangeHour.End))
                        {
                            var valueAfterTemp = totalPrice;
                            if (model.planDetailViewModel.UseEarlyCheckinDayNight.isUsed == true)
                            {
                                var currentHotelDayNightStartTime = currentHotel.DayNightStartTime ?? default(TimeSpan);
                                var numberOfHourDayNightEarlyCheckin = currentHotelDayNightStartTime.Hours - rangeHour.Start.Hour;
                                var dayNightEarlyCheckin = model.planDetailViewModel.UseEarlyCheckinDayNight.adjustment.Find(adj => adj.NumberOfHour == numberOfHourDayNightEarlyCheckin);
                                if (dayNightEarlyCheckin != null)
                                {
                                    valueAfterTemp = valueAfterTemp + dayNightEarlyCheckin.Price;
                                    hourRemainByDayNightPriceFormula.FormulaEarlyCheckIn = new Dictionary<int, decimal>();
                                    hourRemainByDayNightPriceFormula.FormulaEarlyCheckIn.Add(numberOfHourDayNightEarlyCheckin, dayNightEarlyCheckin.Price);
                                }
                            }
                            var currentHotelDayNightCheckOutTime = currentHotel.DayNightEndTime ?? default(TimeSpan);
                            var numberOfHourDayNightAdjustment = rangeHour.End.Hour - currentHotelDayNightCheckOutTime.Hours;
                            var dayNightAdjustment = model.planDetailViewModel.UseDayNightPrice.adjustment.Find(adj => adj.NumberOfHour == numberOfHourDayNightAdjustment);
                            if (dayNightAdjustment != null)
                            {
                                valueAfterTemp = valueAfterTemp + dayNightAdjustment.Price;
                                hourRemainByDayNightPriceFormula.FormulaLateCheckOut = new Dictionary<int, decimal>();
                                hourRemainByDayNightPriceFormula.FormulaLateCheckOut.Add(numberOfHourDayNightAdjustment, dayNightAdjustment.Price);
                            }
                            valueAfterTemp = valueAfterTemp + model.planDetailViewModel.UseDayNightPrice.price ?? 0.0M;
                            hourRemainByDayNightPriceFormula.ValueAfter = valueAfterTemp;
                            formulaList.Add(hourRemainByDayNightPriceFormula);
                        }
                    }


                    Formula finalFormula = new Formula();

                    decimal minPrice = (Math.Min(Math.Min(hourRemainByHourlyPriceFormula.ValueAfter, hourRemainByFullDayPriceFormula.ValueAfter), hourRemainByDayNightPriceFormula.ValueAfter));

                    if (minPrice == hourRemainByHourlyPriceFormula.ValueAfter)
                    {
                        finalFormula = hourRemainByHourlyPriceFormula;

                    }
                    else if (minPrice == hourRemainByFullDayPriceFormula.ValueAfter)
                    {
                        finalFormula = hourRemainByFullDayPriceFormula;

                    }
                    else
                    {
                        finalFormula = hourRemainByDayNightPriceFormula;

                    }

                    finalFormula.ValueBefore = totalPrice;
                    finalFormula.Range = rangeHour;
                    totalPrice = finalFormula.ValueAfter;
                    this.hourlyFormula = new HourlyFormula
                    {
                        hourlyFormulaList = formulaList,
                        finalFormula = finalFormula
                    };
                }
            }

            public void CalculateTraveller()
            {
                
                var currentUser = Common.UserUtitily.getUserProfile();
                extraAdults = new ExtraTraveller();
                extraChildren = new ExtraTraveller();
                RoomTypes roomType = context.RoomTypes.Where(rt => rt.HotelId == currentUser.HotelId && rt.RoomTypeId == model.planDetailViewModel.Plan.RoomTypeId).FirstOrDefault();

                if (model.planDetailViewModel.UseExtraAdultPrice.isUsed && model.adults >= roomType.DefaultAdults)
                {
                    extraAdults.Extra = model.adults - roomType.DefaultAdults;
                    extraAdults.DefaultPrice = model.planDetailViewModel.UseExtraAdultPrice.extraAdultPrice ?? 0;
                    extraAdults.ExtraPrice = extraAdults.Extra * extraAdults.DefaultPrice;
                    totalPrice = totalPrice + extraAdults.ExtraPrice;
                }

                if (model.planDetailViewModel.UseExtraChildPrice.isUsed && model.children >= roomType.DefaultChilds)
                {
                    extraChildren.Extra = model.children - roomType.DefaultChilds;
                    extraChildren.DefaultPrice = model.planDetailViewModel.UseExtraChildPrice.extraChildPrice ?? 0;
                    extraChildren.ExtraPrice = extraChildren.Extra * extraChildren.DefaultPrice;
                    totalPrice = totalPrice + extraChildren.ExtraPrice;
                }


            }
        }
    }
